using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using log4net;
using Spring.Core.IO;

namespace Common
{
    /// <summary>
    /// Custom assembly resource only allow load assembly from preloaded appdomain assembly caches
    /// </summary>
    public class CustomAssemblyResourceFromCache : AbstractResource
    {
        #region Fields

        /// <summary>
        /// The assembly.
        /// </summary>
        private readonly Assembly _assembly;

        /// <summary>
        /// The _resources.
        /// </summary>
        private string[] _resources;

        /// <summary>
        /// The resource name.
        /// </summary>
        private readonly string _resourceName;

        /// <summary>
        /// The full resource name.
        /// </summary>
        private readonly string _fullResourceName;

        /// <summary>
        /// The resource namespace.
        /// </summary>
        private readonly string _resourceNamespace;

        /// <summary>
        /// The resource assembly name.
        /// </summary>
        private readonly string _resourceAssemblyName;

        /// <summary>
        /// Logger for logging
        /// </summary>
        private static readonly ILog Log = LogManager.GetLogger(typeof(AssemblyResource));

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyResource"/> class. 
        /// Creates a new instance of the
        /// <see cref="Spring.Core.IO"/> class.
        /// </summary>
        /// <param name="resourceName">
        /// The name of the assembly resource.
        /// </param>
        /// <exception cref="System">
        /// If the supplied <paramref name="resourceName"/> did not conform
        /// to the expected format.
        /// </exception>
        /// <exception cref="System.IO">
        /// If the assembly specified in the supplied
        /// <paramref name="resourceName"/> was loaded twice with two
        /// different evidences.
        /// </exception>
        /// <exception cref="System.IO">
        /// If the assembly specified in the supplied
        /// <paramref name="resourceName"/> could not be found.
        /// </exception>
        /// <exception cref="System.Security">
        /// If the caller does not have the required permission to load
        /// the assembly specified in the supplied
        /// <paramref name="resourceName"/>.
        /// </exception>
        public CustomAssemblyResourceFromCache(string resourceName)
            : base(resourceName)
        {
            var info = GetResourceNameWithoutProtocol(resourceName).Split('/');
            if (info.Length != 3)
            {
                throw new UriFormatException(
                    string.Format(
                        "Invalid resource name. Name has to be in 'assembly:<assemblyName>/<namespace>/<resourceName>' format:{0}",
                        resourceName));
            }

            this._assembly = AssemblyHelper.GetAssemblyByNameFromCache(info[0]);

            this._fullResourceName = resourceName;
            this._resourceAssemblyName = info[0];
            this._resourceNamespace = info[1];
            this._resourceName = String.Format("{0}.{1}", info[1], info[2]);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Return an <see cref="System.IO.Stream"/> for this resource.
        /// </summary>
        /// <value>
        /// An <see cref="System.IO.Stream"/>.
        /// </value>
        /// <exception cref="System.IO.IOException">
        /// If the stream could not be opened.
        /// </exception>
        /// <exception cref="System.Security.SecurityException">
        /// If the caller does not have the required permission to load
        /// the underlying assembly's manifest.
        /// </exception>
        /// <seealso cref="Spring.Core.IO.IInputStreamSource"/>
        /// <see cref="System.Reflection.Assembly.GetManifestResourceStream(string)"/> 
        public override Stream InputStream
        {
            get
            {
                var stream = this._assembly.GetManifestResourceStream(this._resourceName);
                if (stream == null)
                {
                    Log.Error(
                        "Could not load resource with name = [" + this._resourceName + "] from assembly + " +
                        this._assembly);
                    Log.Error(
                        "URI specified = [" + this._fullResourceName +
                        "] Spring.NET URI syntax is 'assembly://assemblyName/namespace/resourceName'.");
                    Log.Error(
                        "Resource name often has the default namespace prefixed, e.g. 'assembly://MyAssembly/MyNamespace/MyNamespace.MyResource.txt'.");
                }

                return stream;
            }
        }

        /// <summary>
        /// Does the embedded resource specified in the value passed to the
        /// constructor exist?
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this resource actually exists in physical
        /// form (for example on a filesystem).
        /// </value>
        /// <seealso cref="Spring.Core.IO.IResource.Exists"/>
        /// <seealso cref="Spring.Core.IO.IResource.File"/>
        /// <see cref="System.Reflection.Assembly.GetManifestResourceNames()"/> 
        public override bool Exists
        {
            get
            {
                if (this._resources == null)
                {
                    this._resources = this._assembly.GetManifestResourceNames();
                    Array.Sort(this._resources);
                }

                return Array.BinarySearch(this._resources, this._resourceName) >= 0;
            }
        }

        /// <summary>
        /// Does this <see cref="Spring.Core.IO.IResource"/> support relative
        /// resource retrieval?
        /// </summary>
        /// <remarks>
        /// <p>
        /// This implementation does support relative resource retrieval, and
        /// so will always return <see langword="true"/>.
        /// </p>
        /// </remarks>
        /// <value>
        /// <see langword="true"/> if this
        /// <see cref="Spring.Core.IO.IResource"/> supports relative resource
        /// retrieval.
        /// </value>
        /// <seealso cref="Spring.Core.IO.AbstractResource.SupportsRelativeResources"/>
        protected override bool SupportsRelativeResources
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets the root location of the resource (the assembly name in this
        /// case).
        /// </summary>
        /// <value>
        /// The root location of the resource.
        /// </value>
        /// <seealso cref="Spring.Core.IO.AbstractResource.RootLocation"/>
        protected override string RootLocation
        {
            get
            {
                return this._resourceAssemblyName;
            }
        }

        /// <summary>
        /// Gets the current path of the resource (the namespace in which the
        /// target resource was embedded in this case).
        /// </summary>
        /// <value>
        /// The current path of the resource.
        /// </value>
        /// <seealso cref="Spring.Core.IO.AbstractResource.ResourcePath"/>
        protected override string ResourcePath
        {
            get
            {
                return this._resourceNamespace;
            }
        }

        /// <summary>
        /// Gets those characters that are valid path separators for the
        /// resource type.
        /// </summary>
        /// <value>
        /// Those characters that are valid path separators for the resource
        /// type.
        /// </value>
        /// <seealso cref="Spring.Core.IO.AbstractResource.PathSeparatorChars"/>
        protected override char[] PathSeparatorChars
        {
            get
            {
                return new[] { '.' };
            }
        }

        /// <summary>
        /// Returns a description for this resource.
        /// </summary>
        /// <value>
        /// A description for this resource.
        /// </value>
        /// <seealso cref="Spring.Core.IO.IResource.Description"/>
        public override string Description
        {
            get
            {
                return string.Format(
                    CultureInfo.InvariantCulture,
                    "assembly [{0}], resource [{1}]",
                    this._assembly.FullName,
                    this._resourceName);
            }
        }

        #endregion

        /// <summary>
        /// Does the supplied <paramref name="resourceName"/> relative ?
        /// </summary>
        /// <param name="resourceName">
        /// The name of the resource to test.
        /// </param>
        /// <returns>
        /// <see langword="true"/> if resource name is relative; 
        /// otherwise <see langword="false"/>.
        /// </returns>
        protected override bool IsRelativeResource(string resourceName)
        {
            return resourceName.StartsWith("./") || resourceName.StartsWith("/") || resourceName.StartsWith("../") ||
                   resourceName.Split('/').Length != 3;
        }
    }
}